Implement cargo-update
authorAlex Crichton <alex@alexcrichton.com>
Thu, 31 Jul 2014 22:19:20 +0000 (15:19 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Thu, 31 Jul 2014 22:39:52 +0000 (15:39 -0700)
12 files changed:
Cargo.toml
src/bin/cargo-build.rs
src/bin/cargo-doc.rs
src/bin/cargo-run.rs
src/bin/cargo-test.rs
src/bin/cargo-update.rs [new file with mode: 0644]
src/bin/cargo.rs
src/cargo/ops/cargo_compile.rs
src/cargo/ops/cargo_generate_lockfile.rs
src/cargo/ops/mod.rs
src/cargo/sources/git/source.rs
tests/test_cargo_compile_git_deps.rs

index d43c5d86eb226c29449c11e1d0ce434828583473..c56dfca0bed39812df4e34787a58f6f9d44555ed 100644 (file)
@@ -83,5 +83,9 @@ test = false
 name = "cargo-generate-lockfile"
 test = false
 
+[[bin]]
+name = "cargo-update"
+test = false
+
 [[test]]
 name = "tests"
index 20de1bea4ddd31c9b8971442d2bd141e303bcabf..7261a20df9efc34946c4d8635b53d2a3867a2695 100644 (file)
@@ -26,7 +26,7 @@ Options:
     -j N, --jobs N          The number of jobs to run in parallel
     --release               Build artifacts in release mode, with optimizations
     --target TRIPLE         Build for the target triple
-    -u, --update-remotes    Update all remote packages before compiling
+    -u, --update-remotes    Deprecated option, use `cargo update` instead
     --manifest-path PATH    Path to the manifest to compile
     -v, --verbose           Use verbose output
 ",  flag_jobs: Option<uint>, flag_target: Option<String>,
index afae8fc297c0109610db19b676c4bbb8a2d241a0..f5fa11f5d59f7e3762738d59f8ba3a2b281e6249 100644 (file)
@@ -23,7 +23,7 @@ Options:
     -h, --help              Print this message
     --no-deps               Don't build documentation for dependencies
     -j N, --jobs N          The number of jobs to run in parallel
-    -u, --update-remotes    Update all remote packages before compiling
+    -u, --update-remotes    Deprecated option, use `cargo update` instead
     --manifest-path PATH    Path to the manifest to document
     -v, --verbose           Use verbose output
 
index 0b4a06ce053b8868da42272383f094766df05a30..546e793c52cfc32fd962f346cba9674607b31117 100644 (file)
@@ -22,7 +22,7 @@ Usage:
 Options:
     -h, --help              Print this message
     -j N, --jobs N          The number of jobs to run in parallel
-    -u, --update-remotes    Update all remote packages before compiling
+    -u, --update-remotes    Deprecated option, use `cargo update` instead
     --manifest-path PATH    Path to the manifest to execute
     -v, --verbose           Use verbose output
 
index defcc0305a3b5c92fd7eaf11f727e7dc7813d1a6..66c22812b4aaedfb9a618b762c095dcb3ba70cdd 100644 (file)
@@ -23,7 +23,7 @@ Usage:
 Options:
     -h, --help              Print this message
     -j N, --jobs N          The number of jobs to run in parallel
-    -u, --update-remotes    Update all remote packages before compiling
+    -u, --update-remotes    Deprecated option, use `cargo update` instead
     --manifest-path PATH    Path to the manifest to build tests for
     -v, --verbose           Use verbose output
 
diff --git a/src/bin/cargo-update.rs b/src/bin/cargo-update.rs
new file mode 100644 (file)
index 0000000..7fd4f53
--- /dev/null
@@ -0,0 +1,51 @@
+#![feature(phase)]
+
+extern crate serialize;
+extern crate cargo;
+extern crate docopt;
+#[phase(plugin)] extern crate docopt_macros;
+#[phase(plugin, link)] extern crate log;
+
+use std::os;
+use cargo::ops;
+use cargo::{execute_main_without_stdin};
+use cargo::core::MultiShell;
+use cargo::util::{CliResult, CliError};
+use cargo::util::important_paths::find_root_manifest_for_cwd;
+
+docopt!(Options, "
+Update dependencies as recorded in the local lock file.
+
+Usage:
+    cargo-update [options] [<name>]
+
+Options:
+    -h, --help              Print this message
+    --manifest-path PATH    Path to the manifest to compile
+    -v, --verbose           Use verbose output
+
+This command requires that a `Cargo.lock` already exists as generated by
+`cargo build` or related commands.
+
+If <name> is specified, then a conservative update of the lockfile will be
+performed. This means that only the dependency <name> (and all of its transitive
+dependencies) will be updated. All other dependencies will remain locked at
+their currently recorded versions.
+
+If <name> is not specified, then all dependencies will be re-resolved and
+updated.
+",  flag_manifest_path: Option<String>, arg_name: Option<String>)
+
+fn main() {
+    execute_main_without_stdin(execute, false);
+}
+
+fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
+    debug!("executing; cmd=cargo-update; args={}", os::args());
+    shell.set_verbose(options.flag_verbose);
+    let root = try!(find_root_manifest_for_cwd(options.flag_manifest_path));
+
+    ops::update_lockfile(&root, shell, options.arg_name)
+        .map(|_| None).map_err(|err| CliError::from_boxed(err, 101))
+}
+
index ed711e2ce8e5d39a6df28f8d63e51d9296ab305c..828cfd91c8e4731505a2cf96fab40fdb011576f1 100644 (file)
@@ -41,6 +41,7 @@ Some common cargo commands are:
     new         Create a new cargo project
     run         Build and execute src/main.rs
     test        Run the tests
+    update      Update dependencies listed in Cargo.lock
 
 See 'cargo help <command>' for more information on a specific command.
 ")
index 43a0214ffa8c89f5e308201847533632c5c43ce3..c6f87c923e8c0f811b5fafbe0da9e0fbf52d6bf8 100644 (file)
 
 use std::os;
 use std::collections::HashMap;
-use std::io::File;
-use serialize::Decodable;
-use rstoml = toml;
 
 use core::registry::PackageRegistry;
-use core::{MultiShell, Source, SourceId, PackageSet, Target, PackageId, Resolve, resolver};
+use core::{MultiShell, Source, SourceId, PackageSet, Target, PackageId};
+use core::resolver;
 use ops;
 use sources::{PathSource};
 use util::config::{Config, ConfigValue};
-use util::{CargoResult, Wrap, config, internal, human, ChainError, toml};
+use util::{CargoResult, Wrap, config, internal, human, ChainError};
 use util::profile;
 
 pub struct CompileOptions<'a> {
@@ -51,6 +49,11 @@ pub fn compile(manifest_path: &Path,
 
     log!(4, "compile; manifest-path={}", manifest_path.display());
 
+    if options.update {
+        return Err(human("The -u flag has been deprecated, please use the \
+                          `cargo update` command instead"));
+    }
+
     let mut source = PathSource::for_path(&manifest_path.dir_path());
 
     try!(source.update());
@@ -76,7 +79,7 @@ pub fn compile(manifest_path: &Path,
 
         let mut registry = PackageRegistry::new(&mut config);
 
-        let resolved = match try!(load_lockfile(&lockfile, source_id)) {
+        let resolved = match try!(ops::load_lockfile(&lockfile, source_id)) {
             Some(r) => {
                 try!(registry.add_sources(r.iter().map(|p| {
                     p.get_source_id().clone()
@@ -145,21 +148,6 @@ pub fn compile(manifest_path: &Path,
     Ok(test_executables)
 }
 
-fn load_lockfile(path: &Path, sid: &SourceId) -> CargoResult<Option<Resolve>> {
-    // If there is no lockfile, return none.
-    let mut f = match File::open(path) {
-        Ok(f) => f,
-        Err(_) => return Ok(None)
-    };
-
-    let s = try!(f.read_to_string());
-
-    let table = rstoml::Table(try!(toml::parse(s.as_slice(), path)));
-    let mut d = rstoml::Decoder::new(table);
-    let v: resolver::EncodableResolve = Decodable::decode(&mut d).unwrap();
-    Ok(Some(try!(v.to_resolve(sid))))
-}
-
 fn source_ids_from_config(configs: &HashMap<String, config::ConfigValue>,
                           cur_path: Path) -> CargoResult<Vec<SourceId>> {
     debug!("loaded config; configs={}", configs);
index b792dea5fdead4bffcdcd19722f2354175110fb8..e1c3c67d3e94196b7a1f12848ecdf39122c93b2e 100644 (file)
@@ -1,13 +1,17 @@
+#![warn(warnings)]
+use std::collections::HashSet;
 use std::io::File;
 
-use serialize::Encodable;
-use toml::{mod, Encoder};
+use serialize::{Encodable, Decodable};
+use toml::Encoder;
+use rstoml = toml;
 
 use core::registry::PackageRegistry;
-use core::{MultiShell, Source, Resolve, resolver, Package};
+use core::{MultiShell, Source, Resolve, resolver, Package, SourceId};
+use core::PackageId;
 use sources::{PathSource};
 use util::config::{Config};
-use util::{CargoResult};
+use util::{CargoResult, toml, human};
 
 pub fn generate_lockfile(manifest_path: &Path,
                          shell: &mut MultiShell,
@@ -43,9 +47,80 @@ pub fn write_resolve(pkg: &Package, resolve: &Resolve) -> CargoResult<()> {
     let mut e = Encoder::new();
     resolve.encode(&mut e).unwrap();
 
-    let out = toml::Table(e.toml).to_string();
+    let out = rstoml::Table(e.toml).to_string();
     let loc = pkg.get_root().join("Cargo.lock");
     try!(File::create(&loc).write_str(out.as_slice()));
 
     Ok(())
 }
+
+pub fn update_lockfile(manifest_path: &Path,
+                       shell: &mut MultiShell,
+                       to_update: Option<String>) -> CargoResult<()> {
+    let mut source = PathSource::for_path(&manifest_path.dir_path());
+    try!(source.update());
+    let package = try!(source.get_root_package());
+
+    let lockfile = package.get_root().join("Cargo.lock");
+    let source_id = package.get_package_id().get_source_id();
+    let resolve = match try!(load_lockfile(&lockfile, source_id)) {
+        Some(resolve) => resolve,
+        None => return Err(human("A Cargo.lock must exist before it is updated"))
+    };
+
+    let mut config = try!(Config::new(shell, true, None, None));
+    let mut registry = PackageRegistry::new(&mut config);
+
+    let sources = match to_update {
+        Some(name) => {
+            let mut to_avoid = HashSet::new();
+            match resolve.deps(package.get_package_id()) {
+                Some(deps) => {
+                    for dep in deps.filter(|d| d.get_name() == name.as_slice()) {
+                        fill_with_deps(&resolve, dep, &mut to_avoid);
+                    }
+                }
+                None => {}
+            }
+            resolve.iter().filter(|pkgid| !to_avoid.contains(pkgid))
+                   .map(|pkgid| pkgid.get_source_id().clone()).collect()
+        }
+        None => package.get_source_ids(),
+    };
+    try!(registry.add_sources(sources));
+
+    let resolve = try!(resolver::resolve(package.get_package_id(),
+                                         package.get_dependencies(),
+                                         &mut registry));
+
+    try!(write_resolve(&package, &resolve));
+    return Ok(());
+
+    fn fill_with_deps<'a>(resolve: &'a Resolve, dep: &'a PackageId,
+                          set: &mut HashSet<&'a PackageId>) {
+        if !set.insert(dep) { return }
+        match resolve.deps(dep) {
+            Some(mut deps) => {
+                for dep in deps {
+                    fill_with_deps(resolve, dep, set);
+                }
+            }
+            None => {}
+        }
+    }
+}
+
+pub fn load_lockfile(path: &Path, sid: &SourceId) -> CargoResult<Option<Resolve>> {
+    // If there is no lockfile, return none.
+    let mut f = match File::open(path) {
+        Ok(f) => f,
+        Err(_) => return Ok(None)
+    };
+
+    let s = try!(f.read_to_string());
+
+    let table = rstoml::Table(try!(toml::parse(s.as_slice(), path)));
+    let mut d = rstoml::Decoder::new(table);
+    let v: resolver::EncodableResolve = Decodable::decode(&mut d).unwrap();
+    Ok(Some(try!(v.to_resolve(sid))))
+}
index e47fc7cdecc0e1d2a08c476842f2b445aed9f163..ba69961b9bba325086775cc3b643061e441198c2 100644 (file)
@@ -6,6 +6,7 @@ pub use self::cargo_run::run;
 pub use self::cargo_new::{new, NewOptions};
 pub use self::cargo_doc::{doc, DocOptions};
 pub use self::cargo_generate_lockfile::{generate_lockfile, write_resolve};
+pub use self::cargo_generate_lockfile::{update_lockfile, load_lockfile};
 
 mod cargo_clean;
 mod cargo_compile;
index 7e7e0e848a23e47ead0921fea7e321d8cd40c373..032b36cd8181151a8023711fc0f9060e3891d998 100644 (file)
@@ -161,18 +161,16 @@ impl<'a, 'b> Source for GitSource<'a, 'b> {
                                              self.reference.as_slice());
         let should_update = self.config.update_remotes() || actual_rev.is_err();
 
-        let repo = if should_update {
+        let (repo, actual_rev) = if should_update {
             try!(self.config.shell().status("Updating",
                 format!("git repository `{}`", self.remote.get_location())));
 
             log!(5, "updating git source `{}`", self.remote);
-            try!(self.remote.checkout(&self.db_path))
+            let repo = try!(self.remote.checkout(&self.db_path));
+            let rev = try!(repo.rev_for(self.reference.as_slice()));
+            (repo, rev)
         } else {
-            self.remote.db_at(&self.db_path)
-        };
-        let actual_rev = match actual_rev {
-            Ok(rev) => rev,
-            Err(..) => try!(repo.rev_for(self.reference.as_slice())),
+            (self.remote.db_at(&self.db_path), actual_rev.unwrap())
         };
 
         try!(repo.copy_to(actual_rev.clone(), &self.checkout_path));
index 060bfdf0eb9d31e4f3a81b5754910f78219d21d0..c4499683333df848382c25c375550c07292a3ddf 100644 (file)
@@ -1,4 +1,4 @@
-use std::io::{fs, File};
+use std::io::File;
 
 use support::{ProjectBuilder, ResultTest, project, execs, main_file, paths};
 use support::{cargo_dir};
@@ -544,11 +544,14 @@ test!(recompilation {
                                             FRESH, git_project.root().display(),
                                             FRESH, p.root().display())));
 
-    assert_that(p.process(cargo_dir().join("cargo-build")).arg("-u"),
-                execs().with_stdout(format!("{} git repository `file:{}`\n\
-                                             {} bar v0.5.0 (file:{}#[..])\n\
+    assert_that(p.process(cargo_dir().join("cargo-update")),
+                execs().with_stdout(format!("{} git repository `file:{}`",
+                                            UPDATING,
+                                            git_project.root().display())));
+
+    assert_that(p.process(cargo_dir().join("cargo-build")),
+                execs().with_stdout(format!("{} bar v0.5.0 (file:{}#[..])\n\
                                              {} foo v0.5.0 (file:{})\n",
-                                            UPDATING, git_project.root().display(),
                                             FRESH, git_project.root().display(),
                                             FRESH, p.root().display())));
 
@@ -558,23 +561,22 @@ test!(recompilation {
     git_project.process("git").args(["commit", "-m", "test"]).exec_with_output()
                .assert();
 
-    assert_that(p.process(cargo_dir().join("cargo-build")).arg("-u"),
-                execs().with_stdout(format!("{} git repository `file:{}`\n\
-                                             {} bar v0.5.0 (file:{}#[..])\n\
+    println!("compile after commit");
+    assert_that(p.process(cargo_dir().join("cargo-build")),
+                execs().with_stdout(format!("{} bar v0.5.0 (file:{}#[..])\n\
                                              {} foo v0.5.0 (file:{})\n",
-                                            UPDATING, git_project.root().display(),
                                             FRESH, git_project.root().display(),
                                             FRESH, p.root().display())));
 
-    println!("one last time");
-
-    // Remove the lockfile and make sure that we update
-    fs::unlink(&p.root().join("Cargo.lock")).assert();
-    assert_that(p.process(cargo_dir().join("cargo-build")).arg("-u"),
-                execs().with_stdout(format!("{} git repository `file:{}`\n\
-                                             {} bar v0.5.0 (file:{}#[..])\n\
+    // Update the dependency and carry on!
+    assert_that(p.process(cargo_dir().join("cargo-update")),
+                execs().with_stdout(format!("{} git repository `file:{}`",
+                                            UPDATING,
+                                            git_project.root().display())));
+    println!("going for the last compile");
+    assert_that(p.process(cargo_dir().join("cargo-build")),
+                execs().with_stdout(format!("{} bar v0.5.0 (file:{}#[..])\n\
                                              {} foo v0.5.0 (file:{})\n",
-                                            UPDATING, git_project.root().display(),
                                             COMPILING, git_project.root().display(),
                                             COMPILING, p.root().display())));
 })